﻿using JetBrains.Annotations;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WorkFlowCore.Authorization;
using WorkFlowCore.IRepositories;

namespace WorkFlowCore.Framework.Repositories
{
    public class BasicRepository<TEntity, TKey> : IBasicRepository<TEntity, TKey>, IReadOnlyBasicRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>
    {

        private readonly UnitOfWork unitOfWork;
        private readonly IWorkflowSession session;
        private IUnitOfWork unitOfWork1;

        static BasicRepository()
        {
            var data = ReadData();
            VirtualDB.Init<TEntity>(data);
        }

        public BasicRepository(IUnitOfWork unitOfWork1)
        {
            this.unitOfWork1 = unitOfWork1;
        }

        public BasicRepository(UnitOfWork unitOfWork, IWorkflowSession session)
        {
            this.unitOfWork = unitOfWork;
            this.session = session;
        }

        public static void ClearData()
        {
            VirtualDB.ClearData();
        }

        private static List<TEntity> ReadData()
        {
            var dir = Path.Combine(Environment.CurrentDirectory, "Data");
            if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
            var path = Path.Combine(dir, typeof(TEntity).FullName);
            if (!File.Exists(path)) return new List<TEntity>();
            try
            {
                return JsonConvert.DeserializeObject<List<TEntity>>(File.ReadAllText(path, Encoding.Default));
            }
            catch (Exception ex)
            {
                return new List<TEntity>();
            }
        }
        private static void WriteData()
        {
            VirtualDB.SaveData();
        }


        public async Task DeleteAsync(TKey id, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            lock ($"BaseRepository-delete-{typeof(TEntity).FullName}")
            {
                var item = VirtualDB.GetData<TEntity>(d => d.Id.Equals(id)).FirstOrDefault();
                if (item != null)
                {
                    if (cancellationToken.IsCancellationRequested) return;
                    if (unitOfWork.IsActive())
                        unitOfWork.Remove(item);
                    else
                        VirtualDB.Remove(typeof(TEntity), item);
                    if (!unitOfWork.IsActive())
                        WriteData();
                }
            }
        }

        public async Task DeleteAsync([global::JetBrains.Annotations.NotNullAttribute] TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            await DeleteAsync(entity.Id, autoSave, cancellationToken);
        }

        public async Task DeleteManyAsync([global::JetBrains.Annotations.NotNullAttribute] IEnumerable<TKey> ids, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            foreach (var id in ids)
            {
                await DeleteAsync(id, autoSave, cancellationToken);
            }
        }

        public async Task DeleteManyAsync([global::JetBrains.Annotations.NotNullAttribute] IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            await DeleteManyAsync(entities.Select(e => e.Id), autoSave, cancellationToken);
        }

        public async Task DeleteManyAsync([NotNull] Expression<Func<TEntity, bool>> predicate, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var ids = VirtualDB.GetData<TEntity>(d => predicate != null ? predicate.Compile().Invoke(d) : true).Select(e => e.Id).ToList();
            await DeleteManyAsync(ids, autoSave, cancellationToken);
        }

        public async Task<TEntity> FindAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default)
        {
            return VirtualDB.GetData<TEntity>(d => d.Id.Equals(id)).FirstOrDefault();
        }

        public async Task<TEntity> GetAsync(TKey id, bool includeDetails = true, CancellationToken cancellationToken = default)
        {
            return VirtualDB.GetData<TEntity>(d => d.Id.Equals(id)).FirstOrDefault();
        }

        public async Task<TEntity> GetAsync([NotNull] Expression<Func<TEntity, bool>> predicate, bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            return (await GetListAsync(predicate, includeDetails, cancellationToken)).FirstOrDefault();
        }

        public async Task<long> GetCountAsync(CancellationToken cancellationToken = default)
        {
            return VirtualDB.GetData<TEntity>().Count();
        }

        public async Task<long> GetCountAsync([NotNull] Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
        {
            return VirtualDB.GetData<TEntity>(d => predicate != null ? predicate.Compile().Invoke(d) : true).Count();
        }

        public async Task<List<TEntity>> GetListAsync(bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            return VirtualDB.GetData<TEntity>();
        }

        public async Task<List<TEntity>> GetListAsync([global::JetBrains.Annotations.NotNullAttribute] Expression<Func<TEntity, bool>> predicate, bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            return VirtualDB.GetData<TEntity>(d => predicate != null ? predicate.Compile().Invoke(d) : true);
        }

        public async Task<List<TEntity>> GetPagedListAsync(int skipCount, int maxResultCount, string sorting, bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            return VirtualDB.GetData<TEntity>().Skip(skipCount).Take(maxResultCount).ToList();
        }

        public async Task<List<TEntity>> GetPagedListAsync([NotNull] Expression<Func<TEntity, bool>> predicate, int skipCount, int maxResultCount, string sorting, bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            return new List<TEntity>(VirtualDB.GetData<TEntity>(d => predicate != null ? predicate.Compile().Invoke(d) : true).Skip(skipCount).Take(maxResultCount));
        }

        public async Task<TEntity> InsertAsync([global::JetBrains.Annotations.NotNullAttribute] TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            lock ($"BaseRepository-add-{typeof(TEntity).FullName}")
            {
                if (entity is IWithBaseInfoEntity<TKey>)
                {
                    var withBaseInfoEntity = (IWithBaseInfoEntity<TKey>)entity;
                    withBaseInfoEntity.CreationTime = DateTime.Now;
                    withBaseInfoEntity.CreatedUserId = session.User.Id;
                    entity = (TEntity)withBaseInfoEntity;
                }
                if (unitOfWork.IsActive())
                    unitOfWork.Add(entity);
                else
                    VirtualDB.Add(entity.GetType(), entity);
                if (!unitOfWork.IsActive())
                    WriteData();
                return entity;
            }
        }

        public async Task InsertManyAsync([global::JetBrains.Annotations.NotNullAttribute] IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            foreach (var entity in entities)
            {
                await InsertAsync(entity, autoSave, cancellationToken);
            }
        }

        public async Task<TEntity> UpdateAsync([global::JetBrains.Annotations.NotNullAttribute] TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var item = VirtualDB.GetData<TEntity>(d => d.Id.Equals(entity.Id)).FirstOrDefault();
            if (item != null)
            {
                if (unitOfWork.IsActive())
                    unitOfWork.Remove(item);
                else
                    VirtualDB.Remove(item.GetType(), item);
                if (entity is IWithBaseInfoEntity<TKey>)
                {
                    var withBaseInfoEntity = (IWithBaseInfoEntity<TKey>)entity;
                    withBaseInfoEntity.ModifiedTime = DateTime.Now;
                    withBaseInfoEntity.ModifiedUserId = session.User.Id;
                    entity = (TEntity)withBaseInfoEntity;
                }
                if (unitOfWork.IsActive())
                    unitOfWork.Add(entity);
                else
                    VirtualDB.Add(entity.GetType(), entity);
                if (!unitOfWork.IsActive())
                    WriteData();
                return entity;
            }
            return item;
        }

        public async Task UpdateManyAsync([global::JetBrains.Annotations.NotNullAttribute] IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            foreach (var entity in entities)
            {
                await UpdateAsync(entity, autoSave, cancellationToken);
            }
        }
    }

    public class BasicRepository<TEntity> : BasicRepository<TEntity, Guid>, IReadOnlyBasicRepository<TEntity, Guid> where TEntity : class, IEntity<Guid>
    {
        public BasicRepository(UnitOfWork unitOfWork, IWorkflowSession session) : base(unitOfWork, session)
        {
        }
    }
}
